﻿using System.Linq;
using Infrastructure.Persistence;
using SocialFront.Core.Domain;
using SocialFront.Core.Persistence;

namespace SocialFront.Core.Commands.FooCommands
{
    public class UpdateFooCommand : Command
    {
        public Foo Foo { get; set; }

        public UpdateFooCommand(IRepository store) 
            : base(store)
        {
        }

        protected override void ExecuteChange()
        {
            // Load original parent including the child item collection     
            var originalParent = Repository.Query<Foo>().FirstOrDefault<Foo>(x => x.Id == Foo.Id);
            updateFooGraph(Foo, originalParent);

            Repository.SaveChanges();
        }

        private void updateFooGraph(Foo @new, Foo original)
        {
            // Update scalars and complex types - the simple stuff...
            var entry = SocialFrontDataContextFactory.GetDataContext().Entry(original);
            entry.CurrentValues.SetValues(@new);

            if (@new.Siblings != null)
            {
                // Update collection items. 
                foreach (var sibling in @new.Siblings)
                {
                    // Now search for the original child item.
                    var originalChildItem = original.Siblings.SingleOrDefault(c => c.Id == sibling.Id && c.Id != 0);

                    // Does this child already exist?       
                    if (originalChildItem != null)
                    {
                        // Yes -> Update the Foo Sibling itself. 
                        updateFooSibling(sibling, originalChildItem);
                    }
                    else
                    {
                        // No -> It's a new child item -> Insert    
                        // This will insert all new grandchildren as well. 
                        original.Siblings.Add(sibling);
                    }
                }
            }

            // The general approach is if the new sibling collection is null, we don't want to mess
            // with it.  If it's not null and empty, all items get removed assuming the original 
            // collection is non-null.
            if (original.Siblings != null && @new.Siblings != null)
            {
                // Delete collection items.
                foreach (var childItem in original.Siblings.Where(c => c.Id != 0).ToList())
                {
                    // Are there child items in the DB which are NOT in the new child item collection anymore?         
                    if (!@new.Siblings.Any(c => c.Id == childItem.Id))
                        // Yes -> It's a deleted child item -> Delete             
                        deleteFooSibling(childItem);
                }
            }

            if (@new.Locations != null)
            {
                // Update collection items. 
                foreach (var location in @new.Locations)
                {
                    // Now search for the original child item.
                    var originalChildItem = original.Locations.SingleOrDefault(c => c.Id == location.Id && c.Id != 0);

                    // If location already exists no action taken.  
                    if (originalChildItem == null)
                    {
                        // Otherwise, add the location to the foo 
                        // to create the relationship.
                        original.Locations.Add(location);
                    }
                }
            }

            // The general approach is if the new sibling collection is null, we don't want to mess
            // with it.  If it's not null and empty, all items get removed assuming the original 
            // collection is non-null
            if (@new.Locations != null && original.Locations != null)
            {
                // Delete collection items.
                foreach (var childItem in original.Locations.Where(c => c.Id != 0).ToList())
                {
                    // Are there child items in the DB which are NOT in the new child item collection anymore?         
                    if (!@new.Locations.Any(c => c.Id == childItem.Id))
                        // Yes -> It's a deleted child item -> Destroy the relationship.             
                        original.Locations.Remove(childItem);
                }
            }
        }

        private void updateFooSibling(FooSibling @new, FooSibling original)
        {
            if (original != null)
            {
                // Update scalars and complex types - the simple stuff...
                var entry = SocialFrontDataContextFactory.GetDataContext().Entry(original);
                entry.CurrentValues.SetValues(@new);
            }

            if (@new.Siblings != null)
            {
                foreach (var sibling in @new.Siblings)
                {
                    // Now search for the original child item.
                    var originalChildItem = original.Siblings.SingleOrDefault(c => c.Id == sibling.Id && c.Id != 0);

                    // Does this child already exist?       
                    if (originalChildItem != null)
                    {
                        // Yes -> Update the Foo Sibling itself. 
                        updateFooSiblingSibling(sibling, originalChildItem);
                    }
                    else
                    {
                        // No -> It's a new child item -> Insert    
                        Repository.Insert(sibling);
                    }
                }
            }

            if (original != null && original.Siblings != null && @new.Siblings != null)
            {
                // Delete collection items.
                foreach (var childItem in original.Siblings.Where(c => c.Id != 0).ToList())
                {
                    // Are there child items in the DB which are NOT in the new child item collection anymore? 

                    if (!@new.Siblings.Any(c => c.Id == childItem.Id))
                        // Yes -> It's a deleted child item -> Remove from parent to null out foreign key.     
                        // In this type of relationship, we do not want to blow away the sibling, just 
                        // null out his foreign key.  
                        childItem.FooSiblingId = null;
                }
            }
        }

        private void updateFooSiblingSibling(FooSiblingSibling @new, FooSiblingSibling original)
        {
            // Update scalars and complex types - the simple stuff...
            var entry = SocialFrontDataContextFactory.GetDataContext().Entry(original);
            entry.CurrentValues.SetValues(@new);

            // FooSiblingSibling has nothing but scalars.
        }

        private void deleteFooSibling(FooSibling entity)
        {
            // Destroy relationships on all sibling nodes first.
            if (entity.Siblings != null)
            {
                foreach (var grandsibling in entity.Siblings.ToList())
                {
                    // In EF CF, removal of a sibling by convention blows away
                    // the relationship.  In this case, since we explicitly
                    // don't want to cascade on delete, this is ideal. 
                    entity.Siblings.Remove(grandsibling);
                }
            }
            Repository.Delete(entity);
        }
    }
}
